﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

using System.Xml;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using MegavaniaX.Rendering;

namespace MegavaniaX.World
{
    /// <summary>
    /// A (portion of) the game world.
    /// </summary>
    [Serializable]
    public class Map
    {
        public List<Screen> Screens;

        [NonSerialized]
        [ContentSerializerIgnore]
        public IList<Actor> Actors;

        [NonSerialized]
        [ContentSerializerIgnore]
        public Screen CurrentScreen;

        [NonSerialized]
        [ContentSerializerIgnore]
        public Screen NextScreen;

        [NonSerialized]
        [ContentSerializerIgnore]
        UInt64 frameCounter;

        public Map()
        {
            Actors = new List<Actor>();
            frameCounter = 0;
        }


        public void Update(TimeSpan elapsedTime, MegavaniaCamera Camera, bool UpdateActors)
        {
            if (Camera == null)
                throw new MegavaniaException("WorldManager not initialized, or camera was null");

            // update
            foreach (var actor in Actors)
            {
                if (UpdateActors)
                {
                    actor.Update(elapsedTime, CurrentScreen);
                    if (actor.IsAIActor)
                    {
                        (actor as IAIActor).AIUpdate(null);
                    }
                }

                if (actor.ActorID == "player")
                {
                    Camera.Update(elapsedTime, actor, CurrentScreen);
                }
                //MegavaniaX.logger.Debug(string.Format("Actor {4} has position ({5}, {6}), acceleration ({0}, {1}) and velocity ({2}, {3})", 
                //    actor.acceleration.X, actor.acceleration.Y, actor.velocity.X, actor.velocity.Y, actor.ActorID, actor.position.X, actor.position.Y));
            }

            // detect collisions
            DetectCollisions();
        }


        /// <summary>
        /// Gets the next screen adjacent to CurrentScreen based on direction.
        /// </summary>
        /// <param name="direction"></param>
        public Screen GetAdjacentScreen(char direction, float playerX, float playerY)
        {
            if (direction == 'R')
            {
                var screensToRight = Screens.Where(next => next.OffsetX == CurrentScreen.OffsetX + CurrentScreen.Width && next.IsInYRange(playerY));
                if (screensToRight.Count() == 0)
                    return null;

                return screensToRight.First();
            }
            if (direction == 'L')
            {
                var screensToLeft = Screens.Where(next => next.OffsetX + next.Width == CurrentScreen.OffsetX && next.IsInYRange(playerY));
                if (screensToLeft.Count() == 0)
                    return null;

                return screensToLeft.First();
            }
            if (direction == 'T')
            {
                var screensToTop = Screens.Where(next => next.OffsetY + next.Height == CurrentScreen.OffsetY && next.IsInXRange(playerX));
                if (screensToTop.Count() == 0)
                    return null;

                return screensToTop.First();
            }
            if (direction == 'B')
            {
                var screensToRight = Screens.Where(next => next.OffsetY == CurrentScreen.OffsetY + CurrentScreen.Height && next.IsInXRange(playerX));
                if (screensToRight.Count() == 0)
                    return null;

                return screensToRight.First();
            }

            throw new InvalidOperationException("Unknown direction " + direction);
        }

        public void Draw(SpriteBatch spriteBatch, MegavaniaCamera camera)
        {
            frameCounter++;

            foreach (Tile tile in CurrentScreen.Tiles)
            {
                Vector2 tileCoords = CurrentScreen.GetTileCoords(tile);
                if (camera.IsInViewport(tileCoords.X, tileCoords.Y, tile.Width, tile.Height))
                {
                    Vector2 screenCoordinates = camera.GameCoordinatesToScreenCoordinates(tileCoords);
                    if (tile.CurrentTexture(frameCounter) != null)
                        spriteBatch.Draw(tile.CurrentTexture(frameCounter), screenCoordinates, tile.CurrentTextureRectangle(frameCounter), Color.White, 0.0f, Vector2.Zero, camera.ZoomFactor, SpriteEffects.None, 0);
                }
            }
            if (NextScreen != null)
            {
                foreach (Tile tile in NextScreen.Tiles)
                {
                    Vector2 tileCoords = NextScreen.GetTileCoords(tile);
                    if (camera.IsInViewport(tileCoords.X, tileCoords.Y, tile.Width, tile.Height))
                    {
                        Vector2 screenCoordinates = camera.GameCoordinatesToScreenCoordinates(tileCoords);
                        if (tile.CurrentTexture(frameCounter) != null)
                            spriteBatch.Draw(tile.CurrentTexture(frameCounter), screenCoordinates, tile.CurrentTextureRectangle(frameCounter), Color.White, 0.0f, Vector2.Zero, camera.ZoomFactor, SpriteEffects.None, 0);
                    }
                }

            }


        }

        /// <summary>
        /// Detects any collisions between actors.
        /// </summary>
        private void DetectCollisions()
        {
            for (int i = 0; i < Actors.Count; i++)
            {
                var actor = Actors[i];
                for (int j = i + 1; j < Actors.Count; j++)
                {
                    var colliding = Actors[j];
                    if (Util.ActorUtil.AreColliding(actor, colliding))
                    {
                        if ((actor.IsTakesDamage && colliding.IsDealsDamage) || (actor.IsDealsDamage && colliding.IsTakesDamage))
                        {
                            //MegavaniaX.logger.DebugFormat("Handling case of actor {0} colliding with {1}", actor.ActorID, colliding.ActorID);
                            IDealsDamage dealsDamage;
                            ITakesDamage takesDamage;
                            if (actor.IsTakesDamage)
                            {
                                takesDamage = (ITakesDamage)actor;
                                dealsDamage = (IDealsDamage)colliding;
                            }
                            else // actor.IsDealsDamage
                            {
                                takesDamage = (ITakesDamage)colliding;
                                dealsDamage = (IDealsDamage)actor;
                            }
                            takesDamage.TakeDamage(dealsDamage);
                            if (takesDamage.TotalHealth > 0)
                                dealsDamage.OnDamageNoDeath();
                            else
                                dealsDamage.OnDamageDied();
                        }
                    }
                }
            }
        }


    }
}
